/*
 * JTetris.java
 *
 * This work is free software; you can redistribute it and/or
 * modify it under the terms of the Apache License as published 
 * by the Apache Software Foundation.
 *
 * This work is distributed in the hope that it will be useful, 
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 
 * Apache License for more details.
 *
 * Copyright (c) 2011 Tran Hoang Anh (tranhoanganh89@gmail.com). 
 * All rights reserved.
 */
package net.hoanganh.jbraintetris;

import java.awt.Component;
import java.awt.KeyEventDispatcher;
import java.awt.KeyboardFocusManager;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;

import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.UIManager;

/**
 * JTetris - tetris clone game.
 */
public class JTetris {
	JTetrisContainer component = null;
	JTetrisThread thread = null;
	BrainThread brainThread = null;
	GameStatus status = GameStatus.WAITING;
	GameMode mode = GameMode.AUTO;
	Piece piece = null;
	Piece nextPiece = null;
	Brain brain = null;
	int score = 0;
	int level = 1;
	int removedLines = 0;
	JTetrisConfig config;

	public JTetris() {
		config = new JTetrisConfig();
		
		if (config.DEFAULT_MANUAL) {
			mode = GameMode.MANUAL;
		} else {
			mode = GameMode.AUTO;
		}
		
		component = new JTetrisContainer(config);
		component.getControlButton().addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent e) {
				handleControl();
			}
		});
		component.addKeyListener(new KeyAdapter() {
			public void keyPressed(KeyEvent e) {
				handleKeyBoard(e);
			}
		});

		// Key listener is buggy so using key dispatcher instead.
		KeyboardFocusManager manager = KeyboardFocusManager
				.getCurrentKeyboardFocusManager();
		manager.addKeyEventDispatcher(new KeyEventDispatcher() {
			@Override
			public boolean dispatchKeyEvent(KeyEvent e) {
				if (e.getID() == KeyEvent.KEY_RELEASED) {
					handleKeyBoard(e);
				}
				return false;
			}
		});

		handleWait();
	}

	public Component getComponent() {
		return component;
	}

	public boolean isPlaying() {
		return thread != null && !thread.isPaused();
	}

	private void handleWait() {
		status = GameStatus.WAITING;

		component.getBoard().setMessage(config.WAIT_MESSAGE);
		component.getControlButton().setText(config.START);

		component.getPreview().clear();
		component.setScore(0);
		component.setLevel(1);
		component.getBoard().clear();
	}

	private void handleStart() {
		status = GameStatus.PLAYING;

		// Initialize threads
		if (thread == null) {
			thread = new JTetrisThread(config.INIT_INTERVAL);
			thread.start();
		} else {
			thread.reset();
			thread.setPaused(false);
		}

		if (mode == GameMode.AUTO) {
			if (brain == null) {
				brain = new DefaultBrain();
			}
			if (brainThread == null) {
				brainThread = new BrainThread(config.BRAIN_INTERVAL);
				brainThread.start();				
			} else {
				brainThread.setPaused(false);
			}
		} else {
			brain = null;
			if (brainThread != null) {
				brainThread.setPaused(true);
			}
		}
		
		// Init board
		component.getControlButton().setText(config.PAUSE);
		component.getBoard().setMessage(null);
		component.getPreview().clear();
		component.getBoard().clear();
		
		score = 0;
		level = 1;
		handleUpdateScore(0);
		handleUpdateLevel();
		
		if (mode == GameMode.AUTO) {
			if (brain == null) {
				brain = new DefaultBrain();				
			}
			brain.configure();
		}

		nextPiece = PieceFactory.getRandom();
		handleFigureNew();

	}

	private void handleFigureDown() {
		component.getBoard().moveDown();
	}

	private void handleFigureLanded() {
		component.getBoard().commit();

		int removeNum = component.getBoard().removeFullLine();
		removedLines += removeNum;
		handleUpdateScore(removeNum);
		handleUpdateLevel();

		if (!component.getBoard().isEmptyLine(0)) {
			handleGameOver();
		} else {
			handleFigureNew();
		}
	}

	private void handleFigureNew() {
		piece = nextPiece;
		component.getBoard().setPiece(piece);
		nextPiece = PieceFactory.getRandom();
		component.getPreview().paintPiece(nextPiece);
		if (mode == GameMode.AUTO) {
			if (brain != null) {
				brain.calculateBestMove(component.getBoard());
			}
		}		
	}

	private void handlePause() {
		status = GameStatus.PAUSING;
		thread.setPaused(true);
		component.getControlButton().setText(config.START);
		component.getBoard().setMessage(config.PAUSED_MESSAGE);

		if (mode == GameMode.AUTO) {
			brainThread.setPaused(true);
		}
	}

	private void handleContinue() {
		status = GameStatus.PLAYING;
		thread.setPaused(false);
		component.getBoard().setMessage(null);
		component.getControlButton().setText(config.PAUSE);

		if (mode == GameMode.MANUAL) {
			if (brainThread != null) {
				brainThread.setPaused(true);
			}
		} else {
			if (brainThread != null) {
				brainThread.setPaused(false);
			}
		}
	}

	private void handleGameOver() {
		status = GameStatus.GAMEOVER;

		thread.setPaused(true);
		component.getControlButton().setText(config.START);
		component.getBoard().setMessage(config.GAME_OVER);

		if (mode == GameMode.MANUAL) {
			if (brainThread != null) {
				brainThread.setPaused(true);
			}
		}
	}

	/**
	 * Scoring by following rules.
	 * <p>
	 * <table border=1>
	 * <tr>
	 * <th>Removed lines</th>
	 * <th>Score</th>
	 * </tr>
	 * <tr>
	 * <td>1</td>
	 * <td>10</td>
	 * </tr>
	 * <tr>
	 * <td>2</td>
	 * <td>40</td>
	 * </tr>
	 * <tr>
	 * <td>3</td>
	 * <td>80</td>
	 * </tr>
	 * <tr>
	 * <td>4</td>
	 * <td>120</td>
	 * </tr>
	 * </table>
	 * 
	 * @param removeLineNum
	 *            number of rows that is removed
	 */
	private void handleUpdateScore(int removeLineNum) {
		// Scoring
		switch (removeLineNum) {
		case 1:
			score += 10;
			break;
		case 2:
			score += 40;
			break;
		case 3:
			score += 80;
			break;
		case 4:
			score += 120;
			break;
		}

		// Update score
		component.setScore(score);
	}

	private void handleUpdateLevel() {
		level = score / config.SCORE_PER_LEVEL + 1;
		if (level <= config.MAXIMUM_LEVEL) {
			int adjustInterval = 4500 / (level + 5) - 250;
			thread.setInterval(adjustInterval);
		}
		component.setLevel(level);
	}

	private synchronized void handleKeyBoard(KeyEvent e) {
		if (!isPlaying()) {
			return;
		}

		if (mode == GameMode.AUTO) {
			return;
		}

		switch (e.getKeyCode()) {
		case KeyEvent.VK_LEFT:
			component.getBoard().moveLeft();
			break;
		case KeyEvent.VK_RIGHT:
			component.getBoard().moveRight();
			break;
		case KeyEvent.VK_DOWN:
			component.getBoard().moveDown();
			break;
		case KeyEvent.VK_UP:
			component.getBoard().rotate();
			break;
		case KeyEvent.VK_SPACE:
			component.getBoard().moveAllDown();
			break;
		}
	}

	private void handleControl() {
		switch (status) {
		case WAITING:
		case GAMEOVER:
			handleStart();
			break;
		case PAUSING:
			handleContinue();
			break;
		case PLAYING:
			handlePause();
			break;
		}
	}

	public void handleExit() {
		thread = null;
		brainThread = null;
	}

	private class JTetrisThread extends GameThread {
		public JTetrisThread(int interval) {
			super(interval);
		}

		public void reset() {
			setInterval(config.INIT_INTERVAL);
		}

		@Override
		protected void handleTimer() {
			if (status == GameStatus.PLAYING) {
				if (!component.getBoard().isLanded()) {
					handleFigureDown();
				} else {
					handleFigureLanded();
				}
			}
		}
	}

	private class BrainThread extends GameThread {

		public BrainThread(int interval) {
			super(interval);
		}

		@Override
		protected void handleTimer() {
			if (status == GameStatus.PLAYING) {
				if (brain != null) {
					brain.doAction(component.getBoard());
				}
			}
		}
	}

	public static void main(String[] args) {
		setNativeUI();

		JFrame main = new JFrame();
		final JTetris game = new JTetris();
		main.add(game.getComponent());
		main.pack();
		main.setTitle("JBrainTetris - Tetris With AI");
		main.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		main.setResizable(false);
		main.setLocationRelativeTo(null);
		main.addWindowListener(new WindowAdapter() {
			@Override
			public void windowClosing(WindowEvent e) {
				super.windowClosing(e);
				game.handleExit();
			}
		});

		main.setVisible(true);
	}

	private static void setNativeUI() {
		try {
			UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
		} catch (Exception e) {
			JOptionPane.showMessageDialog(null, "Can not initialize UI");
			System.exit(0);
		}
	}
}
